<?php
/*
 * File:    parser.class.phps
 * Purpose: Parsering template files into usable form for browser output.
 * Author:  Gyrbo <dennis@gyro.be>
 */

class Parser {
	var $safedir = '../web/templates/default';
	var $defdir = '../web/templates/default';
	var $file;
	var $ar = array();
	var $res_cache = array();

	function Parser($file=NULL,$dir=NULL) {
		$this->file = $file;
		$this->ar =  array('' => '');
		if($dir!==NULL) {
			$this->defdir = $this->safedir = $dir;
		}
	}

	// Load a file
	function load($file) {
		$this->file = $file;
	}

	// Set a new safedir. This should be used when you want to
	// use the parser for your own purposes. You can also set it
	// as the second param when initializing the class
	function SetDir($dir) {
		$this->safedir = $dir;
	}

	function parse() {
		if($this->file==NULL)
			return false;
		//return $this->__parse($this->__parse($this->__tpl_file($this->file), $this->ar), $this->ar, true);
		return $this->__parse($this->__tpl_file($this->file), $this->ar);
	}

	function parsecode($code) {
		if($code==NULL)
			return false;
		return $this->__parse($code, $this->ar, false, true);
	}

	function pparse() {
		echo $this->parse();
	}

	function __lang($str) {
		global $T;
		$n = explode(':', $str);
		$l = &$T;
		foreach($n as $item) {
			if(!is_array($l))
				return NULL;

			$l = &$l[$item];
		}

		return $l;
	}

	function __tpl_file($file) {
		if(is_readable($this->safedir.'/'.$file)) {
			return $this->__preparse(file_get_contents($this->safedir.'/'.$file));
		} elseif(is_readable($this->defdir.'/'.$file)) {
			return $this->__preparse(file_get_contents($this->defdir.'/'.$file));
		} else {
			return '{@NO SUCH FILE:'.$file.'@}';
		}
	}

	function __preparse($tpl) {
		// normalize newlines
		$tpl = strtr($tpl, array("\r\n" => "\n", "\r" => "\n"));

		// Remove newlines after tag
		$tpl = preg_replace('@\n[ \t]*({[^\}]+\})[ \t]*\n@', "\n$1", $tpl);

		// Strip comments
		$pos2 = 0;
		while(false !== ($pos = strpos($tpl, '{*', $pos2))) {
			if(false !== ($pos2 = strpos( $tpl, '*}', $pos))) {
				$tpl = substr_replace($tpl, '', $pos, $pos2-$pos+2);
			} else
				$pos2 = $pos+2;
		}

		return $tpl;
	}

	function __parse($code, &$ar, $only_vars=false, $stripextratags=false) {
		global $T;

		$next = 0;
		while(($start=strpos($code, '{', $next))!==false) {
			if(($end = strpos($code, '}', $start+1))!==false) {
				//We have an opening tag
				$tag = substr($code, $start+1, $end-$start-1);

				//Error checking, see if there is no { in the tag (!!! bad !!!)
				if(($start1=strrpos($tag, '{'))!==false) {
				    // skip to internal tag
				    $start = $start + $start1 + 1;
				    $tag = substr($code, $start+1, $end-$start-1);
				}
				$tagl = strlen($tag);

				if(isset($ar[$tag])) {
					//Tag exists in array
					if(is_array($ar[$tag])) {
						//It's a CLASS

						//We have a closing tag
						if($only_vars || (($start2=strpos($code, '{/'.$tag.'}', $end))===false)) {
							//missing closing tag
							$next = $end+1;
							continue;
						}
						$end2 = $start2 + strlen($tag) + 3;

						$count = count($ar[$tag]);

						$cont = '';

//						for($i=0;$i<$count;$i++) {
						foreach($ar[$tag] as $val) {
//							if($name!='_')
								$cont .= $this->__parse(substr($code, $end+1, $start2 - $end-1), $val);
						}

						$code = substr_replace($code, $cont, $start, $end2-$start);
						$next = $start;
					} elseif(is_bool($ar[$tag])) {
						//It's an IF

						//We have a closing tag
						if($only_vars || (($start2=strpos($code, '{/'.$tag.'}', $end))===false)) {
							//missing closing tag
							$next = $end+1;
							continue;
						}
						$end2 = $start2 + strlen($tag) + 3;

						$split = explode('{/'.$tag.'/}', substr($code, $end+1, $start2 - $end-1), 2);
						if($ar[$tag]) {
							$code = substr_replace($code, $split[0], $start, $end2-$start);
							$next = $start;
						}else{
							if(isset($split[1])) {
								$code = substr_replace($code, $split[1], $start, $end2-$start);
								$next = $start;
							} else {
								$code = substr_replace($code, '', $start, $end2-$start);
								$next = $start;
							}
						}
					} else {
						//It's a VAR
						$code = substr_replace($code, $ar[$tag], $start, $end-$start+1);
						$next = $start/* + strlen($ar[$tag])*/;
					}
				} elseif($tag[0]=='$' && ($start2 = strpos($code, '{/'.$tag.'}', $end))!==false) {
					//It's a set variable
					$var = substr($tag, 1);
					$value = substr($code, $end+1, $start2 - $end-1);
					$ar[$var] = $value;

					$end2 = $start2 + strlen($tag) + 3;

					if($code[$end2]=="\n")
						$end2++;

					$code = substr_replace($code, '', $start, $end2-$start);

					$next = $start;
				} elseif($tag[0]=='!') {
					//It's a wrap template
					$name = substr($tag, 1);
					if(false !== ($start2 = strpos($code, '{/!'.$name.'}', $end))) {
						// inside = what's between {!wrap.tpl} and {/!wrap.tpl}
						$inside = substr($code, $end+1, $start2-$end-1);

						// wrap_tpl = wrap.tpl with {main page} replaced by $inside
						$wrap_tpl = str_replace('{main page}', $inside, $this->__tpl_file($name));

						$end2 = $start2 + strlen($tag) + 3;

						if($code[$end2]=="\n")
							$end2++;

						// replace {!wrap.tpl}...{/!wrap.tpl} by $wrap_tpl
						$code = substr_replace($code, $wrap_tpl, $start, $end2-$start);

						//cleanup
						unset($inside);
						unset($wrap_tpl);

						$next = $start;
					} else {
						//$before = substr($code, 0, $start);
						$after = substr($code, $end+1);

						$wrap_tpl = str_replace('{main page}', $after, $this->__tpl_file($name));
						//$code = $before.$wrap_tpl;
						$code = substr_replace($code, $wrap_tpl, $start);

						//cleanup
						unset($before);
						unset($after);
						unset($wrap_tpl);

						$next = $start;
					}
				} elseif($tag[0]=='#') {
					//It's an include template
					$name = substr($tag, 1);

					if($code[$end]=="\n")
						$end++;

					$code = substr_replace($code, $this->__tpl_file($name), $start, $end-$start+1);

					$next = $start;
				} elseif($tag[0]=='L' && $tag[1]==':' && !is_null($stag = $this->__lang(substr($tag, 2)))){
					//It's a language tag
					$code = substr_replace($code, $stag, $start, $end-$start+1);
					$next = $start/* + strlen($T[$stag])*/;
				} elseif($tag[0]=='~') {
					if(substr($tag, -1)=='~') {
						// VAR
						$short = substr($tag, 1, -1);

						if(false !== ($start2 = strpos($code, '{/~'.$short.'~}', $end))) {
							$inside = substr($code, $end+1, $start2-$end-1);

							if(!isset($this->ar[$short]))
								$this->ar[$short] = $inside;

							$end2 = $start2 + strlen($tag) + 3;
							// replace {~var~}...{/~var~} by {var}
							$code = substr_replace($code, '{'.$short.'}', $start, $end2-$start);
							$next = $start;
							continue;
						} elseif(!isset($this->ar[$short]))
							$this->ar[$short] = '';
					} else {
						// IF or CLASS
						$short = substr($tag, 1);
						if(!isset($this->ar[$short]))
							$this->ar[$short] = false;
					}
					$code = substr_replace($code, '{'.$short.'}', $start, $end-$start+1);
					$next = $start;
				}else{
					//Nothing matches, leave as-is
					if ($stripextratags) {
						if(($start2=strpos($code, '{/'.$tag.'}', $end))===false) {
							//Not sure this is really a tag, keep
							$next = $end+1;
						} else {
							//We can be almost certain this is a tag, remove
							$end = $start2 + strlen($tag) + 3;
							$code = substr_replace($code, '', $start, $end-$start);
							$next = $start;
						}
					} else {
						$next = $end+1;
					}
				}
			}
		}

		//Do a second pass, only replacing variables
		//return $this->__parse($code, $ar, false);
		return $code;
	}

	function &_resolve_node($node) {
		if(empty($node))
			return $this->ar;

		if(isset($this->res_cache[$node]))
			return $this->res_cache[$node];

		$n = explode('::', $node);
		$l = &$this->ar;
		foreach($n as $item) {
			if(isset($l[$item])) {
				$l = &$l[$item][count($l[$item])-1];
			}else{
				$l[$item] = array();
				$l = &$l[$item][0];
			}
		}

		$this->res_cache[$node] = &$l;

		return $l;
	}

	//////////////////////////////////////////////////
	// Add variables to a node
	//////////////////////////////////////////////////
	function AddVar($name, $val, $node='') {
		$l = &$this->_resolve_node($node);
		$l[$name] = $val;
	}

	//////////////////////////////////////////////////
	// Add a new subclass to the defined node
	//////////////////////////////////////////////////
	function AddClass($name, $node='') {
		$l = &$this->_resolve_node($node);
		if(!isset($l[$name]))
			$l[$name] = array(array());
	}

	//////////////////////////////////////////////////
	// Move the pointer to the next item in a class
	//////////////////////////////////////////////////
	function NextClass($name, $node='') {
		$l = &$this->_resolve_node($node);

		$l[$name][] = array();

		//Quick and dirty way to clear the cache
		$this->res_cache = array();
	}

	//////////////////////////////////////////////////
	// Remove the current item of a class
	//////////////////////////////////////////////////
	function StripClass($name, $node='') {
		$l = &$this->_resolve_node($node);

		unset($l[$name][count($l[$name])-1]);

		//Quick and dirty way to clear the cache
		$this->res_cache = array();
	}

	//////////////////////////////////////////////////
	// Add an array of variables to a class
	//////////////////////////////////////////////////
	function AddArray($ar, $node='') {
		$l = &$this->_resolve_node($node);

		$l = array_merge($l, $ar);
	}

	//////////////////////////////////////////////////
	// Set the state of an if
	//////////////////////////////////////////////////
	function AddIf($name, $state=true, $node='') {
		$state = (bool)$state;
		$l = &$this->_resolve_node($node);

		$l[$name] = $state;
	}
}

?>
